# -*- coding: utf-8 -*-
"""
Free modules
"""
# ****************************************************************************
#       Copyright (C) 2007      Mike Hansen <mhansen@gmail.com>,
#                     2007-2009 Nicolas M. Thiery <nthiery at users.sf.net>
#                     2010      Christian Stump <christian.stump@univie.ac.at>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#                  https://www.gnu.org/licenses/
# ****************************************************************************

from sage.structure.unique_representation import UniqueRepresentation
from sage.structure.parent import Parent
from sage.structure.indexed_generators import IndexedGenerators, parse_indices_names
from sage.modules.module import Module
from sage.rings.integer import Integer
from sage.structure.element import parent
from sage.modules.with_basis.indexed_element import IndexedFreeModuleElement
from sage.sets.finite_enumerated_set import FiniteEnumeratedSet
from sage.combinat.cartesian_product import CartesianProduct_iters
from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets
from sage.misc.cachefunc import cached_method
from sage.misc.lazy_attribute import lazy_attribute
from sage.categories.morphism import SetMorphism
from sage.categories.all import Category, Sets, ModulesWithBasis, GradedAlgebrasWithBasis
from sage.categories.tensor import tensor
import sage.data_structures.blas_dict as blas
from sage.typeset.ascii_art import AsciiArt, ascii_art
from sage.typeset.unicode_art import UnicodeArt, unicode_art


class CombinatorialFreeModule(UniqueRepresentation, Module, IndexedGenerators):
    r"""
    Class for free modules with a named basis

    INPUT:

    - ``R`` - base ring

    - ``basis_keys`` - list, tuple, family, set, etc. defining the
      indexing set for the basis of this module

    - ``element_class`` - the class of which elements of this module
      should be instances (optional, default None, in which case the
      elements are instances of
      :class:`~sage.modules.with_basis.indexed_element.IndexedFreeModuleElement`)

    - ``category`` - the category in which this module lies (optional,
      default None, in which case use the "category of modules with
      basis" over the base ring ``R``); this should be a subcategory
      of :class:`ModulesWithBasis`

    For the options controlling the printing of elements, see
    :class:`~sage.structure.indexed_generators.IndexedGenerators`.

    .. NOTE::

        These print options may also be accessed and modified using the
        :meth:`print_options` method, after the module has been defined.

    EXAMPLES:

    We construct a free module whose basis is indexed by the letters a, b, c::

        sage: F = CombinatorialFreeModule(QQ, ['a','b','c'])
        sage: F
        Free module generated by {'a', 'b', 'c'} over Rational Field

    Its basis is a family, indexed by a, b, c::

        sage: e = F.basis()
        sage: e
        Finite family {'a': B['a'], 'b': B['b'], 'c': B['c']}

    ::

        sage: [x for x in e]
        [B['a'], B['b'], B['c']]
        sage: [k for k in e.keys()]
        ['a', 'b', 'c']

    Let us construct some elements, and compute with them::

        sage: e['a']
        B['a']
        sage: 2*e['a']
        2*B['a']
        sage: e['a'] + 3*e['b']
        B['a'] + 3*B['b']

    Some uses of
    :meth:`sage.categories.commutative_additive_semigroups.CommutativeAdditiveSemigroups.ParentMethods.summation`
    and :meth:`sum`::

        sage: F = CombinatorialFreeModule(QQ, [1,2,3,4])
        sage: F.summation(F.monomial(1), F.monomial(3))
        B[1] + B[3]

        sage: F = CombinatorialFreeModule(QQ, [1,2,3,4])
        sage: F.sum(F.monomial(i) for i in [1,2,3])
        B[1] + B[2] + B[3]

    Note that free modules with a given basis and parameters are unique::

        sage: F1 = CombinatorialFreeModule(QQ, (1,2,3,4))
        sage: F1 is F
        True

    The identity of the constructed free module depends on the order of the
    basis and on the other parameters, like the prefix. Note that :class:`CombinatorialFreeModule` is
    a :class:`~sage.structure.unique_representation.UniqueRepresentation`. Hence,
    two combinatorial free modules evaluate equal if and only if they are
    identical::

        sage: F1 = CombinatorialFreeModule(QQ, (1,2,3,4))
        sage: F1 is F
        True
        sage: F1 = CombinatorialFreeModule(QQ, [4,3,2,1])
        sage: F1 == F
        False
        sage: F2 = CombinatorialFreeModule(QQ, [1,2,3,4], prefix='F')
        sage: F2 == F
        False

    Because of this, if you create a free module with certain parameters and
    then modify its prefix or other print options, this affects all modules
    which were defined using the same parameters.
    ::

        sage: F2.print_options(prefix='x')
        sage: F2.prefix()
        'x'
        sage: F3 = CombinatorialFreeModule(QQ, [1,2,3,4], prefix='F')
        sage: F3 is F2   # F3 was defined just like F2
        True
        sage: F3.prefix()
        'x'
        sage: F4 = CombinatorialFreeModule(QQ, [1,2,3,4], prefix='F', bracket=True)
        sage: F4 == F2   # F4 was NOT defined just like F2
        False
        sage: F4.prefix()
        'F'

        sage: F2.print_options(prefix='F') #reset for following doctests

    The constructed module is in the category of modules with basis
    over the base ring::

        sage: CombinatorialFreeModule(QQ, Partitions()).category()
        Category of vector spaces with basis over Rational Field

    If furthermore the index set is finite (i.e. in the category
    ``Sets().Finite()``), then the module is declared as being finite
    dimensional::

        sage: CombinatorialFreeModule(QQ, [1,2,3,4]).category()
        Category of finite dimensional vector spaces with basis over Rational Field
        sage: CombinatorialFreeModule(QQ, Partitions(3),
        ....:                         category=Algebras(QQ).WithBasis()).category()
        Category of finite dimensional algebras with basis over Rational Field

    See :mod:`sage.categories.examples.algebras_with_basis` and
    :mod:`sage.categories.examples.hopf_algebras_with_basis` for
    illustrations of the use of the ``category`` keyword, and see
    :class:`sage.combinat.root_system.weight_space.WeightSpace` for an
    example of the use of ``element_class``.

    Customizing print and LaTeX representations of elements::

        sage: F = CombinatorialFreeModule(QQ, ['a','b'], prefix='x')
        sage: original_print_options = F.print_options()
        sage: sorted(original_print_options.items())
        [('bracket', None),
         ('latex_bracket', False), ('latex_prefix', None),
         ('latex_scalar_mult', None), ('prefix', 'x'),
         ('scalar_mult', '*'),
         ('sorting_key', <function ...<lambda> at ...>),
         ('sorting_reverse', False), ('string_quotes', True),
         ('tensor_symbol', None)]

        sage: e = F.basis()
        sage: e['a'] - 3 * e['b']
        x['a'] - 3*x['b']

        sage: F.print_options(prefix='x', scalar_mult=' ', bracket='{')
        sage: e['a'] - 3 * e['b']
        x{'a'} - 3 x{'b'}
        sage: latex(e['a'] - 3 * e['b'])
        x_{a} - 3 x_{b}

        sage: F.print_options(latex_prefix='y')
        sage: latex(e['a'] - 3 * e['b'])
        y_{a} - 3  y_{b}

        sage: F.print_options(sorting_reverse=True)
        sage: e['a'] - 3 * e['b']
        -3 x{'b'} + x{'a'}
        sage: F.print_options(**original_print_options) # reset print options

        sage: F = CombinatorialFreeModule(QQ, [(1,2), (3,4)])
        sage: e = F.basis()
        sage: e[(1,2)] - 3 * e[(3,4)]
        B[(1, 2)] - 3*B[(3, 4)]

        sage: F.print_options(bracket=['_{', '}'])
        sage: e[(1,2)] - 3 * e[(3,4)]
        B_{(1, 2)} - 3*B_{(3, 4)}

        sage: F.print_options(prefix='', bracket=False)
        sage: e[(1,2)] - 3 * e[(3,4)]
        (1, 2) - 3*(3, 4)

    TESTS:

    Before :trac:`14054`, combinatorial free modules violated the unique
    parent condition. That caused a problem. The tensor product construction
    involves maps, but maps check that their domain and the parent of a
    to-be-mapped element are identical (not just equal). However, the tensor
    product was cached by a :class:`~sage.misc.cachefunc.cached_method`, which
    involves comparison by equality (not identity). Hence, the last line of
    the following example used to fail with an assertion error::

        sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix="F")
        sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix="G")
        sage: f =   F.monomial(1) + 2 * F.monomial(2)
        sage: g = 2*G.monomial(3) +     G.monomial(4)
        sage: tensor([f, g])
        2*F[1] # G[3] + F[1] # G[4] + 4*F[2] # G[3] + 2*F[2] # G[4]
        sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix='x')
        sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix='y')
        sage: f =   F.monomial(1) + 2 * F.monomial(2)
        sage: g = 2*G.monomial(3) +     G.monomial(4)
        sage: tensor([f, g])
        2*x[1] # y[3] + x[1] # y[4] + 4*x[2] # y[3] + 2*x[2] # y[4]

    We check that we can use the shorthand ``C.<a,b,...> = ...``::

        sage: C.<x,y,z> = CombinatorialFreeModule(QQ)
        sage: C
        Free module generated by {'x', 'y', 'z'} over Rational Field
        sage: a = x - y + 4*z; a
        x - y + 4*z
        sage: a.parent() is C
        True

    TESTS::

        sage: XQ = SchubertPolynomialRing(QQ)
        sage: XZ = SchubertPolynomialRing(ZZ)
        sage: XQ == XZ
        False
        sage: XQ == XQ
        True

    We check that ticket :trac:`28681` is fixed::

        sage: F = CombinatorialFreeModule(ZZ, ZZ); F.rename("F")
        sage: FF = tensor((F,F))
        sage: cartesian_product((FF,FF))
        F # F (+) F # F
    """

    @staticmethod
    def __classcall_private__(cls, base_ring, basis_keys=None, category=None,
                              prefix=None, names=None, **keywords):
        """
        TESTS::

            sage: F = CombinatorialFreeModule(QQ, ['a','b','c'])
            sage: G = CombinatorialFreeModule(QQ, ('a','b','c'))
            sage: F is G
            True

            sage: F = CombinatorialFreeModule(QQ, ['a','b','c'], latex_bracket=['LEFT', 'RIGHT'])
            sage: F.print_options()['latex_bracket']
            ('LEFT', 'RIGHT')

            sage: F is G
            False

        We check that the category is properly straightened::

            sage: F  = CombinatorialFreeModule(QQ, ['a','b'])
            sage: F1 = CombinatorialFreeModule(QQ, ['a','b'], category = ModulesWithBasis(QQ))
            sage: F2 = CombinatorialFreeModule(QQ, ['a','b'], category = [ModulesWithBasis(QQ)])
            sage: F3 = CombinatorialFreeModule(QQ, ['a','b'], category = (ModulesWithBasis(QQ),))
            sage: F4 = CombinatorialFreeModule(QQ, ['a','b'], category = (ModulesWithBasis(QQ),CommutativeAdditiveSemigroups()))
            sage: F5 = CombinatorialFreeModule(QQ, ['a','b'], category = (ModulesWithBasis(QQ),Category.join((LeftModules(QQ), RightModules(QQ)))))
            sage: F1 is F, F2 is F, F3 is F, F4 is F, F5 is F
            (True, True, True, True, True)

            sage: G  = CombinatorialFreeModule(QQ, ['a','b'], category = AlgebrasWithBasis(QQ))
            sage: F is G
            False
        """
        if isinstance(basis_keys, range):
            basis_keys = tuple(basis_keys)
        if isinstance(basis_keys, (list, tuple)):
            basis_keys = FiniteEnumeratedSet(basis_keys)
        category = ModulesWithBasis(base_ring).or_subcategory(category)
        # bracket or latex_bracket might be lists, so convert
        # them to tuples so that they're hashable.
        bracket = keywords.get('bracket', None)
        if isinstance(bracket, list):
            keywords['bracket'] = tuple(bracket)
        latex_bracket = keywords.get('latex_bracket', None)
        if isinstance(latex_bracket, list):
            keywords['latex_bracket'] = tuple(latex_bracket)

        names, basis_keys, prefix = parse_indices_names(names, basis_keys, prefix, keywords)
        if prefix is None:
            prefix = "B"

        return super(CombinatorialFreeModule, cls).__classcall__(cls,
            base_ring, basis_keys, category=category, prefix=prefix, names=names,
            **keywords)

    Element = IndexedFreeModuleElement

    @lazy_attribute
    def element_class(self):
        """
        The (default) class for the elements of this parent

        Overrides :meth:`Parent.element_class` to force the
        construction of Python class. This is currently needed to
        inherit really all the features from categories, and in
        particular the initialization of ``_mul_`` in
        :meth:`Magmas.ParentMethods.__init_extra__`.

        EXAMPLES::

            sage: A = Algebras(QQ).WithBasis().example(); A
            An example of an algebra with basis:
            the free algebra on the generators ('a', 'b', 'c') over Rational Field

            sage: A.element_class.mro()
            [<class 'sage.categories.examples.algebras_with_basis.FreeAlgebra_with_category.element_class'>,
             <class 'sage.modules.with_basis.indexed_element.IndexedFreeModuleElement'>,
             ...]
            sage: a,b,c = A.algebra_generators()
            sage: a * b
            B[word: ab]

        TESTS::

            sage: A.__class__.element_class.__module__
            'sage.combinat.free_module'
        """
        return self.__make_element_class__(self.Element,
                                           name="%s.element_class" % self.__class__.__name__,
                                           module=self.__class__.__module__,
                                           inherit=True)

    def __init__(self, R, basis_keys=None, element_class=None, category=None,
                 prefix=None, names=None, **kwds):
        r"""
        TESTS::

            sage: F = CombinatorialFreeModule(QQ, ['a','b','c'])

            sage: F.category()
            Category of finite dimensional vector spaces with basis over Rational Field

        One may specify the category this module belongs to::

            sage: F = CombinatorialFreeModule(QQ, ['a','b','c'], category=AlgebrasWithBasis(QQ))
            sage: F.category()
            Category of finite dimensional algebras with basis over Rational Field

            sage: F = CombinatorialFreeModule(GF(3), ['a','b','c'],
            ....:                             category=(Modules(GF(3)).WithBasis(), Semigroups()))
            sage: F.category()
            Join of Category of finite semigroups
                 and Category of finite dimensional modules with basis over Finite Field of size 3
                 and Category of vector spaces with basis over Finite Field of size 3

            sage: F = CombinatorialFreeModule(QQ, ['a','b','c'], category = FiniteDimensionalModulesWithBasis(QQ))
            sage: F.basis()
            Finite family {'a': B['a'], 'b': B['b'], 'c': B['c']}
            sage: F.category()
            Category of finite dimensional vector spaces with basis over Rational Field

            sage: TestSuite(F).run()

        TESTS:

        Regression test for :trac:`10127`: ``self._indices`` needs to be
        set early enough, in case the initialization of the categories
        use ``self.basis().keys()``. This occurred on several occasions
        in non trivial constructions. In the following example,
        :class:`AlgebrasWithBasis` constructs ``Homset(self,self)`` to
        extend by bilinearity method ``product_on_basis``, which in
        turn triggers ``self._repr_()``::

            sage: class MyAlgebra(CombinatorialFreeModule):
            ....:     def _repr_(self):
            ....:         return "MyAlgebra on %s"%(self.basis().keys())
            ....:     def product_on_basis(self,i,j):
            ....:         pass
            sage: MyAlgebra(ZZ, ZZ, category = AlgebrasWithBasis(QQ))
            MyAlgebra on Integer Ring

        A simpler example would be welcome!

        We check that unknown options are caught::

            sage: CombinatorialFreeModule(ZZ, [1,2,3], keyy=2)
            Traceback (most recent call last):
            ...
            ValueError: keyy is not a valid print option.
        """
        # Make sure R is a ring with unit element
        from sage.categories.all import Rings
        if R not in Rings():
            raise TypeError("Argument R must be a ring.")

        if element_class is not None:
            self.Element = element_class

        # The following is to ensure that basis keys is indeed a parent.
        # tuple/list are converted to FiniteEnumeratedSet and set/frozenset to
        # Set
        # (e.g. root systems passes lists)
        basis_keys = Sets()(basis_keys, enumerated_set=True)

        # This is needed for the Cartesian product
        # TODO: Remove this duplication from __classcall_private__
        names, basis_keys, prefix = parse_indices_names(names, basis_keys, prefix, kwds)
        if prefix is None:
            prefix = "B"

        # ignore the optional 'key' since it only affects CachedRepresentation
        kwds.pop('key', None)
        if 'monomial_key' in kwds:
            kwds['sorting_key'] = kwds.pop('monomial_key')
        if 'monomial_reverse' in kwds:
            kwds['sorting_reverse'] = kwds.pop('monomial_reverse')
        IndexedGenerators.__init__(self, basis_keys, prefix, **kwds)

        if category is None:
            category = ModulesWithBasis(R)
        elif isinstance(category, tuple):
            category = Category.join(category)
        if basis_keys in Sets().Finite():
            category = category.FiniteDimensional()

        Parent.__init__(self, base=R, category=category, names=names)

        self._order = None

    # For backwards compatibility
    _repr_term = IndexedGenerators._repr_generator
    _latex_term = IndexedGenerators._latex_generator

    def _ascii_art_term(self, m):
        r"""
        Return an ascii art representation of the term indexed by ``m``.

        TESTS::

            sage: R = NonCommutativeSymmetricFunctions(QQ).R()
            sage: ascii_art(R.one())  # indirect doctest
            1
        """
        try:
            if m == self.one_basis():
                return AsciiArt(["1"])
        except Exception:
            pass
        return IndexedGenerators._ascii_art_generator(self, m)

    def _unicode_art_term(self, m):
        r"""
        Return an unicode art representation of the term indexed by ``m``.

        TESTS::

            sage: R = NonCommutativeSymmetricFunctions(QQ).R()
            sage: unicode_art(R.one())  # indirect doctest
            1
        """
        try:
            if m == self.one_basis():
                return UnicodeArt(["1"])
        except Exception:
            pass
        return IndexedGenerators._unicode_art_generator(self, m)

    # mostly for backward compatibility
    @lazy_attribute
    def _element_class(self):
        """
        TESTS::

            sage: F = CombinatorialFreeModule(QQ, ['a','b','c'])
            sage: F._element_class
            <class 'sage.combinat.free_module.CombinatorialFreeModule_with_category.element_class'>
        """
        return self.element_class

    def _an_element_(self):
        """
        EXAMPLES::

            sage: CombinatorialFreeModule(QQ, ("a", "b", "c")).an_element()
            2*B['a'] + 2*B['b'] + 3*B['c']
            sage: CombinatorialFreeModule(QQ, ("a", "b", "c"))._an_element_()
            2*B['a'] + 2*B['b'] + 3*B['c']
            sage: CombinatorialFreeModule(QQ, ()).an_element()
            0
            sage: CombinatorialFreeModule(QQ, ZZ).an_element()
            3*B[-1] + B[0] + 3*B[1]
            sage: CombinatorialFreeModule(QQ, RR).an_element()
            B[1.00000000000000]
        """
        # Try a couple heuristics to build a not completely trivial
        # element, while handling cases where R.an_element is not
        # implemented, or R has no iterator, or R has few elements.
        x = self.zero()
        I = self.basis().keys()
        R = self.base_ring()
        try:
            x = x + self.monomial(I.an_element())
        except Exception:
            pass
        try:
            g = iter(self.basis().keys())
            for c in range(1, 4):
                x = x + self.term(next(g), R(c))
        except Exception:
            pass
        return x

    # What semantic do we want for containment?
    # Accepting anything that can be coerced is not reasonable, especially
    # if we allow coercion from the enumerated set.
    # Accepting anything that can be converted is an option, but that would
    # be expensive. So far, x in self if x.parent() == self

    def __contains__(self, x):
        """
        TESTS::

            sage: F = CombinatorialFreeModule(QQ,["a", "b"])
            sage: G = CombinatorialFreeModule(ZZ,["a", "b"])
            sage: F.monomial("a") in F
            True
            sage: G.monomial("a") in F
            False
            sage: "a" in F
            False
            sage: 5/3 in F
            False
        """
        return parent(x) == self  # is self?

    def _element_constructor_(self, x):
        """
        Convert ``x`` into ``self``.

        EXAMPLES::

            sage: F = CombinatorialFreeModule(QQ,["a", "b"])
            sage: F(F.monomial("a"))   # indirect doctest
            B['a']

        Do not rely on the following feature which may be removed in the future::

            sage: QS3 = SymmetricGroupAlgebra(QQ,3)
            sage: QS3([2,3,1])     # indirect doctest
            [2, 3, 1]

        instead, use::

            sage: P = QS3.basis().keys()
            sage: QS3.monomial(P([2,3,1]))   # indirect doctest
            [2, 3, 1]

        or::

            sage: B = QS3.basis()
            sage: B[P([2,3,1])]
            [2, 3, 1]

        TODO: The symmetric group algebra (and in general,
        combinatorial free modules on word-like object could instead
        provide an appropriate short-hand syntax QS3[2,3,1]).

        Rationale: this conversion is ambiguous in situations like::

            sage: F = CombinatorialFreeModule(QQ,[0,1])

        Is ``0`` the zero of the base ring, or the index of a basis
        element?  I.e. should the result be ``0`` or ``B[0]``?

            sage: F = CombinatorialFreeModule(QQ,[0,1])
            sage: F(0) # this feature may eventually disappear
            0
            sage: F(1)
            Traceback (most recent call last):
            ...
            TypeError: do not know how to make x (= 1) an element of Free module generated by ... over Rational Field

        It is preferable not to rely either on the above, and instead, use::

            sage: F.zero()
            0

        Note that, on the other hand, conversions from the ground ring
        are systematically defined (and mathematically meaningful) for
        algebras.

        Conversions between distinct free modules are not allowed any
        more::

            sage: F = CombinatorialFreeModule(ZZ, ["a", "b"]);      F.rename("F")
            sage: G = CombinatorialFreeModule(QQ, ["a", "b"]);      G.rename("G")
            sage: H = CombinatorialFreeModule(ZZ, ["a", "b", "c"]); H.rename("H")
            sage: G(F.monomial("a"))
            Traceback (most recent call last):
            ...
            TypeError: do not know how to make x (= B['a']) an element of self (=G)
            sage: H(F.monomial("a"))
            Traceback (most recent call last):
            ...
            TypeError: do not know how to make x (= B['a']) an element of self (=H)

        Here is a real life example illustrating that this yielded
        mathematically wrong results::

            sage: S = SymmetricFunctions(QQ)
            sage: s = S.s(); p = S.p()
            sage: ss = tensor([s,s]); pp = tensor([p,p])
            sage: a = tensor((s[2],s[2]))

        The following originally used to yield ``p[[2]] # p[[2]]``, and if
        there was no natural coercion between ``s`` and ``p``, this would
        raise a ``NotImplementedError``. Since :trac:`15305`, this takes the
        coercion between ``s`` and ``p`` and lifts it to the tensor product. ::

            sage: pp(a)
            1/4*p[1, 1] # p[1, 1] + 1/4*p[1, 1] # p[2] + 1/4*p[2] # p[1, 1] + 1/4*p[2] # p[2]

        Extensions of the ground ring should probably be reintroduced
        at some point, but via coercions, and with stronger sanity
        checks (ensuring that the codomain is really obtained by
        extending the scalar of the domain; checking that they share
        the same class is not sufficient).

        TESTS:

        Conversion from the ground ring is implemented for algebras::

            sage: QS3 = SymmetricGroupAlgebra(QQ,3)
            sage: QS3(2)
            2*[1, 2, 3]
        """
        R = self.base_ring()

        # Coerce ints to Integers
        if isinstance(x, int):
            x = Integer(x)

        if x in R:
            if x == 0:
                return self.zero()
            else:
                raise TypeError("do not know how to make x (= %s) an element of %s" % (x, self))
        # x is an element of the basis enumerated set;
        # This is a very ugly way of testing this
        elif ((hasattr(self._indices, 'element_class') and
               isinstance(self._indices.element_class, type) and
               isinstance(x, self._indices.element_class)) or
              parent(x) == self._indices):
            return self.monomial(x)
        elif x in self._indices:
            return self.monomial(self._indices(x))
        else:
            if hasattr(self, '_coerce_end'):
                try:
                    return self._coerce_end(x)
                except TypeError:
                    pass
            raise TypeError("do not know how to make x (= %s) an element of self (=%s)" % (x, self))

    def _convert_map_from_(self, S):
        """
        Implement the conversion from formal sums.

        EXAMPLES::

            sage: E = CombinatorialFreeModule(QQ, ["a", "b", "c"])
            sage: f = FormalSum([[2, "a"], [3, "b"]]); f
            2*a + 3*b
            sage: E._convert_map_from_(f.parent())
            Generic morphism:
              From: Abelian Group of all Formal Finite Sums over Integer Ring
              To:   Free module generated by {'a', 'b', 'c'} over Rational Field
            sage: E(f)
            2*B['a'] + 3*B['b']

            sage: E._convert_map_from_(ZZ)
        """
        from sage.structure.formal_sum import FormalSums
        K = self.base_ring()
        if isinstance(S, FormalSums) and K.has_coerce_map_from(S.base_ring()):
            G = self.basis().keys()
            return SetMorphism(S.Hom(self, category=self.category() | S.category()),
                               lambda x: self.sum_of_terms((G(g), K(c))
                                                           for c, g in x))

    def _an_element_impl(self):  # TODO: REMOVE?
        """
        Return an element of ``self``, namely the zero element.

        EXAMPLES::

            sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'])
            sage: F._an_element_impl()
            0
            sage: _.parent() is F
            True
        """
        return self.element_class(self, {})

    def _first_ngens(self, n):
        """
        Used by the preparser for ``F.<x> = ...``.

        EXAMPLES::

            sage: C = CombinatorialFreeModule(QQ, ZZ)
            sage: C._first_ngens(3)
            (B[0], B[1], B[-1])

            sage: R.<x,y> = FreeAlgebra(QQ, 2)
            sage: x,y
            (x, y)
        """
        try:
            # Try gens first for compatibility with classes that
            #   rely on this (e.g., FreeAlgebra)
            return tuple(self.gens())[:n]
        except (AttributeError, ValueError, TypeError):
            pass
        B = self.basis()
        it = iter(self._indices)
        return tuple(B[next(it)] for i in range(n))

    def dimension(self):
        """
        Return the dimension of the free module (which is given
        by the number of elements in the basis).

        EXAMPLES::

            sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'])
            sage: F.dimension()
            3
            sage: F.basis().cardinality()
            3
            sage: F.basis().keys().cardinality()
            3

        Rank is available as a synonym::

            sage: F.rank()
            3

        ::

            sage: s = SymmetricFunctions(QQ).schur()
            sage: s.dimension()
            +Infinity
        """
        return self._indices.cardinality()

    rank = dimension

    def is_exact(self):
        r"""
        Return ``True`` if elements of ``self`` have exact representations,
        which is true of ``self`` if and only if it is true of
        ``self.basis().keys()`` and ``self.base_ring()``.

        EXAMPLES::

            sage: GroupAlgebra(GL(3, GF(7))).is_exact()
            True
            sage: GroupAlgebra(GL(3, GF(7)), RR).is_exact()
            False
            sage: GroupAlgebra(GL(3, pAdicRing(7))).is_exact() # not implemented correctly (not my fault)!
            False
        """
        # The index set may not have a check for exactness
        # Assume the index set is exact if we cannot check
        try:
            if not self.basis().keys().is_exact():
                return False
        except AttributeError:
            pass
        return self.base_ring().is_exact()

    def set_order(self, order):
        """
        Set the order of the elements of the basis.

        If :meth:`set_order` has not been called, then the ordering is
        the one used in the generation of the elements of self's
        associated enumerated set.

        .. WARNING::

            Many cached methods depend on this order, in
            particular for constructing subspaces and quotients.
            Changing the order after some computations have been
            cached does not invalidate the cache, and is likely to
            introduce inconsistencies.

        EXAMPLES::

            sage: QS2 = SymmetricGroupAlgebra(QQ,2)
            sage: b = list(QS2.basis().keys())
            sage: b.reverse()
            sage: QS2.set_order(b)
            sage: QS2.get_order()
            [[2, 1], [1, 2]]
        """
        self._order = order
        from sage.combinat.ranker import rank_from_list
        self._rank_basis = rank_from_list(self._order)

    @cached_method
    def get_order(self):
        """
        Return the order of the elements in the basis.

        EXAMPLES::

            sage: QS2 = SymmetricGroupAlgebra(QQ,2)
            sage: QS2.get_order()                    # note: order changed on 2009-03-13
            [[2, 1], [1, 2]]
        """
        if self._order is None:
            self.set_order(self.basis().keys().list())
        return self._order

    def get_order_key(self):
        """
        Return a comparison key on the basis indices that is
        compatible with the current term order.

        EXAMPLES::

            sage: A = FiniteDimensionalAlgebrasWithBasis(QQ).example()
            sage: A.set_order(['x', 'y', 'a', 'b'])
            sage: Akey = A.get_order_key()
            sage: sorted(A.basis().keys(), key=Akey)
            ['x', 'y', 'a', 'b']
            sage: A.set_order(list(reversed(A.basis().keys())))
            sage: Akey = A.get_order_key()
            sage: sorted(A.basis().keys(), key=Akey)
            ['b', 'a', 'y', 'x']
        """
        self.get_order()
        return self._order_key

    def _order_key(self, x):
        """
        Return a key for `x` compatible with the term order.

        INPUT:

        - ``x`` -- indices of the basis of ``self``

        EXAMPLES::

            sage: A = CombinatorialFreeModule(QQ, ['x','y','a','b'])
            sage: A.set_order(['x', 'y', 'a', 'b'])
            sage: A._order_key('x')
            0
            sage: A._order_key('y')
            1
            sage: A._order_key('a')
            2
        """
        return self._rank_basis(x)

    def from_vector(self, vector, order=None):
        """
        Build an element of ``self`` from a (sparse) vector.

        .. SEEALSO:: :meth:`get_order`, :meth:`CombinatorialFreeModule.Element._vector_`

        EXAMPLES::

            sage: QS3 = SymmetricGroupAlgebra(QQ, 3)
            sage: b = QS3.from_vector(vector((2, 0, 0, 0, 0, 4))); b
            2*[1, 2, 3] + 4*[3, 2, 1]
            sage: a = 2*QS3([1,2,3])+4*QS3([3,2,1])
            sage: a == b
            True
        """
        if order is None:
            order = self.get_order()
        return self._from_dict({order[index]: coeff
                                for (index, coeff) in vector.items()})

    def sum(self, iter_of_elements):
        """
        Return the sum of all elements in ``iter_of_elements``.

        Overrides method inherited from commutative additive monoid as it
        is much faster on dicts directly.

        INPUT:

        - ``iter_of_elements`` -- iterator of elements of ``self``

        EXAMPLES::

            sage: F = CombinatorialFreeModule(QQ,[1,2])
            sage: f = F.an_element(); f
            2*B[1] + 2*B[2]
            sage: F.sum( f for _ in range(5) )
            10*B[1] + 10*B[2]
        """
        D = blas.sum(element._monomial_coefficients for element in iter_of_elements)
        return self._from_dict(D, remove_zeros=False)

    def linear_combination(self, iter_of_elements_coeff, factor_on_left=True):
        r"""
        Return the linear combination `\lambda_1 v_1 + \cdots +
        \lambda_k v_k` (resp.  the linear combination `v_1 \lambda_1 +
        \cdots + v_k \lambda_k`) where ``iter_of_elements_coeff`` iterates
        through the sequence `((v_1, \lambda_1), ..., (v_k, \lambda_k))`.

        INPUT:

        - ``iter_of_elements_coeff`` -- iterator of pairs ``(element, coeff)``
          with ``element`` in ``self`` and ``coeff`` in ``self.base_ring()``

        - ``factor_on_left`` -- (optional) if ``True``, the coefficients are
          multiplied from the left if ``False``, the coefficients are
          multiplied from the right

        EXAMPLES::

            sage: F = CombinatorialFreeModule(QQ,[1,2])
            sage: f = F.an_element(); f
            2*B[1] + 2*B[2]
            sage: F.linear_combination( (f,i) for i in range(5) )
            20*B[1] + 20*B[2]
        """
        return self._from_dict(blas.linear_combination(((element._monomial_coefficients, coeff)
                                                        for element, coeff in iter_of_elements_coeff),
                                                       factor_on_left=factor_on_left),
                               remove_zeros=False)

    def term(self, index, coeff=None):
        """
        Construct a term in ``self``.

        INPUT:

        - ``index`` -- the index of a basis element
        - ``coeff`` -- an element of the coefficient ring (default: one)

        EXAMPLES::

            sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'])
            sage: F.term('a',3)
            3*B['a']
            sage: F.term('a')
            B['a']

        Design: should this do coercion on the coefficient ring?
        """
        if coeff is None:
            coeff = self.base_ring().one()
        return self._from_dict({index: coeff})

    def _monomial(self, index):
        """
        TESTS::

            sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'])
            sage: F._monomial('a')
            B['a']
        """
        return self._from_dict({index: self.base_ring().one()}, remove_zeros=False)

    @lazy_attribute
    def monomial(self):
        """
        Return the basis element indexed by ``i``.

        INPUT:

        - ``i`` -- an element of the index set

        EXAMPLES::

            sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'])
            sage: F.monomial('a')
            B['a']

        ``F.monomial`` is in fact (almost) a map::

            sage: F.monomial
            Term map from {'a', 'b', 'c'} to Free module generated by {'a', 'b', 'c'} over Rational Field
        """
        # Should use a real Map, as soon as combinatorial_classes are enumerated sets, and therefore parents
        from sage.categories.poor_man_map import PoorManMap
        return PoorManMap(self._monomial, domain=self._indices, codomain=self, name="Term map")

    def sum_of_terms(self, terms, distinct=False):
        """
        Construct a sum of terms of ``self``.

        INPUT:

        - ``terms`` -- a list (or iterable) of pairs ``(index, coeff)``
        - ``distinct`` -- (default: ``False``) whether the indices are
          guaranteed to be distinct

        EXAMPLES::

            sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'])
            sage: F.sum_of_terms([('a',2), ('c',3)])
            2*B['a'] + 3*B['c']

        If ``distinct`` is True, then the construction is optimized::

            sage: F.sum_of_terms([('a',2), ('c',3)], distinct = True)
            2*B['a'] + 3*B['c']

        .. WARNING::

            Use ``distinct=True`` only if you are sure that the
            indices are indeed distinct::

                sage: F.sum_of_terms([('a',2), ('a',3)], distinct = True)
                3*B['a']

        Extreme case::

            sage: F.sum_of_terms([])
            0
        """
        if distinct:
            return self._from_dict(dict(terms))
        return self.sum(self.term(index, coeff) for (index, coeff) in terms)

    @cached_method
    def zero(self):
        """
        EXAMPLES::

            sage: F = CombinatorialFreeModule(QQ, ['a', 'b', 'c'])
            sage: F.zero()
            0
        """
        return self._from_dict({}, remove_zeros=False)

    def _from_dict(self, d, coerce=False, remove_zeros=True):
        r"""
        Construct an element of ``self`` from an ``{index: coefficient}``
        dictionary.

        INPUT:

        - ``d`` -- a dictionary ``{index: coeff}`` where each ``index`` is
          the index of a basis element and each ``coeff`` belongs to the
          coefficient ring ``self.base_ring()``

        - ``coerce`` -- a boolean (default: ``False``), whether to coerce
          the coefficients ``coeff`` to the coefficient ring

        - ``remove_zeros`` -- a boolean (default: ``True``), if some
          coefficients ``coeff`` may be zero and should therefore be removed

        EXAMPLES::

            sage: e = SymmetricFunctions(QQ).elementary()
            sage: s = SymmetricFunctions(QQ).schur()
            sage: a = e([2,1]) + e([1,1,1]); a
            e[1, 1, 1] + e[2, 1]
            sage: s._from_dict(a.monomial_coefficients())
            s[1, 1, 1] + s[2, 1]

        If the optional argument ``coerce`` is ``True``, then the
        coefficients are coerced into the base ring of ``self``::

            sage: part = Partition([2,1])
            sage: d = {part:1}
            sage: a = s._from_dict(d,coerce=True); a
            s[2, 1]
            sage: a.coefficient(part).parent()
            Rational Field

        With ``remove_zeros=True``, zero coefficients are removed::

            sage: s._from_dict({part:0})
            0

        .. WARNING::

            With ``remove_zeros=True``, it is assumed that no
            coefficient of the dictionary is zero. Otherwise, this may
            lead to illegal results::

                sage: list(s._from_dict({part:0}, remove_zeros=False))
                [([2, 1], 0)]
        """
        assert isinstance(d, dict)
        if coerce:
            R = self.base_ring()
            d = {key: R(coeff) for key, coeff in d.items()}
        if remove_zeros:
            d = {key: coeff for key, coeff in d.items() if coeff}
        return self.element_class(self, d)


class CombinatorialFreeModule_Tensor(CombinatorialFreeModule):
        """
        Tensor Product of Free Modules

        EXAMPLES:

        We construct two free modules, assign them short names, and construct their tensor product::

            sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.__custom_name = "F"
            sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.__custom_name = "G"
            sage: T = tensor([F, G]); T
            F # G

            sage: T.category()
            Category of finite dimensional tensor products of modules with basis over Integer Ring

            sage: T.construction() # todo: not implemented
            [tensor, ]

        T is a free module, with same base ring as F and G::

            sage: T.base_ring()
            Integer Ring

        The basis of T is indexed by tuples of basis indices of F and G::

            sage: T.basis().keys()
            Image of Cartesian product of {1, 2}, {3, 4} by <class 'tuple'>
            sage: T.basis().keys().list()
            [(1, 3), (1, 4), (2, 3), (2, 4)]

        FIXME: Should elements of a CartesianProduct be tuples (making them hashable)?

        Here are the basis elements themselves::

            sage: T.basis().cardinality()
            4
            sage: list(T.basis())
            [B[1] # B[3], B[1] # B[4], B[2] # B[3], B[2] # B[4]]

        The tensor product is associative and flattens sub tensor products::

            sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H")
            sage: tensor([F, tensor([G, H])])
            F # G # H
            sage: tensor([tensor([F, G]), H])
            F # G # H
            sage: tensor([F, G, H])
            F # G # H

        We now compute the tensor product of elements of free modules::

            sage: f =   F.monomial(1) + 2 * F.monomial(2)
            sage: g = 2*G.monomial(3) +     G.monomial(4)
            sage: h =   H.monomial(5) +     H.monomial(6)
            sage: tensor([f, g])
            2*B[1] # B[3] + B[1] # B[4] + 4*B[2] # B[3] + 2*B[2] # B[4]

        Again, the tensor product is associative on elements::

            sage: tensor([f, tensor([g, h])]) == tensor([f, g, h])
            True
            sage: tensor([tensor([f, g]), h]) == tensor([f, g, h])
            True

        Note further that the tensor product spaces need not preexist::

            sage: t = tensor([f, g, h])
            sage: t.parent()
            F # G # H


        TESTS::

            sage: tensor([tensor([F, G]), H]) == tensor([F, G, H])
            True
            sage: tensor([F, tensor([G, H])]) == tensor([F, G, H])
            True
        """
        @staticmethod
        def __classcall_private__(cls, modules, **options):
            """
            TESTS::

                sage: F = CombinatorialFreeModule(ZZ, [1,2])
                sage: G = CombinatorialFreeModule(ZZ, [3,4])
                sage: H = CombinatorialFreeModule(ZZ, [4])
                sage: tensor([tensor([F, G]), H]) == tensor([F, G, H])
                True
                sage: tensor([F, tensor([G, H])]) == tensor([F, G, H])
                True

            Check that :trac:`19608` is fixed::

                sage: T = tensor([F, G, H])
                sage: T in Modules(ZZ).FiniteDimensional()
                True
            """
            assert(len(modules) > 0)
            R = modules[0].base_ring()
            assert(all(module in ModulesWithBasis(R)) for module in modules)
            # should check the base ring
            # flatten the list of modules so that tensor(A, tensor(B,C)) gets rewritten into tensor(A, B, C)
            modules = sum([module._sets if isinstance(module, CombinatorialFreeModule_Tensor) else (module,) for module in modules], ())
            if all('FiniteDimensional' in M.category().axioms() for M in modules):
                options['category'] = options['category'].FiniteDimensional()
            return super(CombinatorialFreeModule.Tensor, cls).__classcall__(cls, modules, **options)

        def __init__(self, modules, **options):
            """
            TESTS::

                sage: F = CombinatorialFreeModule(ZZ, [1,2]); F
                F
            """
            self._sets = modules
            indices = CartesianProduct_iters(*[module.basis().keys()
                                               for module in modules]).map(tuple)
            CombinatorialFreeModule.__init__(self, modules[0].base_ring(), indices, **options)
            # the following is not the best option, but it's better than nothing.
            if 'tensor_symbol' in options:
                self._print_options['tensor_symbol'] = options['tensor_symbol']

        def _repr_(self):
            r"""
            This is customizable by setting
            ``self.print_options('tensor_symbol'=...)``.

            TESTS::

                sage: F = CombinatorialFreeModule(ZZ, [1,2,3])
                sage: G = CombinatorialFreeModule(ZZ, [1,2,3,8])
                sage: F.rename("F")
                sage: G.rename("G")
                sage: T = tensor([F, G])
                sage: T # indirect doctest
                F # G
                sage: T.print_options(tensor_symbol=' @ ')  # note the spaces
                sage: T # indirect doctest
                F @ G

            To avoid a side\--effect on another doctest, we revert the change::

                sage: T.print_options(tensor_symbol=' # ')
            """
            from sage.categories.tensor import tensor
            if hasattr(self, "_print_options"):
                symb = self._print_options['tensor_symbol']
                if symb is None:
                    symb = tensor.symbol
            else:
                symb = tensor.symbol
            return symb.join("%s" % module for module in self._sets)
            # TODO: make this overridable by setting _name

        def _ascii_art_(self, term):
            """
            TESTS::

                sage: R = NonCommutativeSymmetricFunctions(QQ).R()
                sage: Partitions.options(diagram_str="#", convention="french")
                sage: s = ascii_art(tensor((R[1,2], R[3,1,2]))); s
                R   # R
                 #     ###
                 ##      #
                         ##

            Check that the breakpoints are correct (:trac:`29202`)::

                sage: s._breakpoints
                [6]
            """
            if hasattr(self, "_print_options"):
                symb = self._print_options['tensor_symbol']
                if symb is None:
                    symb = tensor.symbol
            else:
                symb = tensor.symbol
            return ascii_art(*(module._ascii_art_term(t)
                               for module, t in zip(self._sets, term)),
                             sep=AsciiArt([symb], breakpoints=[len(symb)]))

        _ascii_art_term = _ascii_art_

        def _unicode_art_(self, term):
            """
            TESTS::

                sage: R = NonCommutativeSymmetricFunctions(QQ).R()
                sage: Partitions.options(diagram_str="#", convention="french")
                sage: s = unicode_art(tensor((R[1,2], R[3,1,2]))); s
                R    ⊗ R
                 ┌┐     ┌┬┬┐
                 ├┼┐    └┴┼┤
                 └┴┘      ├┼┐
                          └┴┘

            Check that the breakpoints are correct (:trac:`29202`)::

                sage: s._breakpoints
                [7]
            """
            if hasattr(self, "_print_options"):
                symb = self._print_options['tensor_symbol']
                if symb is None:
                    symb = tensor.unicode_symbol
            else:
                symb = tensor.unicode_symbol
            return unicode_art(*(module._unicode_art_term(t)
                                 for module, t in zip(self._sets, term)),
                               sep=UnicodeArt([symb], breakpoints=[len(symb)]))

        _unicode_art_term = _unicode_art_

        def _latex_(self):
            r"""
            TESTS::

                sage: F = CombinatorialFreeModule(ZZ, [1,2,3])
                sage: G = CombinatorialFreeModule(ZZ, [1,2,3,8])
                sage: F.rename("F")
                sage: G.rename("G")
                sage: latex(tensor([F, F, G])) # indirect doctest
                \text{\texttt{F}} \otimes \text{\texttt{F}} \otimes \text{\texttt{G}}
                sage: F._latex_ = lambda : "F"
                sage: G._latex_ = lambda : "G"
                sage: latex(tensor([F, F, G])) # indirect doctest
                F \otimes F \otimes G
            """
            from sage.misc.latex import latex
            symb = " \\otimes "
            return symb.join("%s" % latex(module) for module in self._sets)

        def _repr_term(self, term):
            """
            TESTS::

                sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix="F")
                sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix="G")
                sage: f =   F.monomial(1) + 2 * F.monomial(2)
                sage: g = 2*G.monomial(3) +     G.monomial(4)
                sage: tensor([f, g]) # indirect doctest
                2*F[1] # G[3] + F[1] # G[4] + 4*F[2] # G[3] + 2*F[2] # G[4]
            """
            if hasattr(self, "_print_options"):
                symb = self._print_options['tensor_symbol']
                if symb is None:
                    symb = tensor.symbol
            else:
                symb = tensor.symbol
            return symb.join(module._repr_term(t) for (module, t) in zip(self._sets, term))

        def _latex_term(self, term):
            r"""
            TESTS::

                sage: F = CombinatorialFreeModule(ZZ, [1,2,3], prefix='x')
                sage: G = CombinatorialFreeModule(ZZ, [1,2,3,4], prefix='y')
                sage: f =   F.monomial(1) + 2 * F.monomial(2)
                sage: g = 2*G.monomial(3) +     G.monomial(4)
                sage: latex(tensor([f, g])) # indirect doctest
                2 x_{1} \otimes y_{3} + x_{1} \otimes y_{4} + 4 x_{2} \otimes y_{3} + 2 x_{2} \otimes y_{4}
            """
            symb = " \\otimes "
            return symb.join(module._latex_term(t) for (module, t) in zip(self._sets, term))

        @cached_method
        def tensor_constructor(self, modules):
            r"""
            INPUT:

             - ``modules`` -- a tuple `(F_1,\dots,F_n)` of
               free modules whose tensor product is self

            Returns the canonical multilinear morphism from
            `F_1 \times \dots \times F_n` to `F_1 \otimes \dots \otimes F_n`

            EXAMPLES::

                sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.__custom_name = "F"
                sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.__custom_name = "G"
                sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H")

                sage: f =   F.monomial(1) + 2 * F.monomial(2)
                sage: g = 2*G.monomial(3) +     G.monomial(4)
                sage: h =   H.monomial(5) +     H.monomial(6)
                sage: FG  = tensor([F, G   ])
                sage: phi_fg = FG.tensor_constructor((F, G))
                sage: phi_fg(f,g)
                2*B[1] # B[3] + B[1] # B[4] + 4*B[2] # B[3] + 2*B[2] # B[4]

                sage: FGH = tensor([F, G, H])
                sage: phi_fgh = FGH.tensor_constructor((F, G, H))
                sage: phi_fgh(f, g, h)
                2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6]

                sage: phi_fg_h = FGH.tensor_constructor((FG, H))
                sage: phi_fg_h(phi_fg(f, g), h)
                2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6]
            """
            assert(module in ModulesWithBasis(self.base_ring()) for module in modules)
            assert(tensor(modules) == self)
            # a list l such that l[i] is True if modules[i] is readily a tensor product
            is_tensor = [isinstance(module, CombinatorialFreeModule_Tensor) for module in modules]
            # the tensor_constructor, on basis elements
            result = self.monomial * CartesianProductWithFlattening(is_tensor)
            # TODO: make this into an element of Hom( A x B, C ) when those will exist
            for i in range(len(modules)):
                result = modules[i]._module_morphism(result, position=i,
                                                     codomain=self)
            return result

        def _tensor_of_elements(self, elements):
            """
            Returns the tensor product of the specified elements.
            The result should be in ``self``.

            EXAMPLES::

                sage: F = CombinatorialFreeModule(ZZ, [1,2]); F.__custom_name = "F"
                sage: G = CombinatorialFreeModule(ZZ, [3,4]); G.__custom_name = "G"
                sage: H = CombinatorialFreeModule(ZZ, [5,6]); H.rename("H")

                sage: f =   F.monomial(1) + 2 * F.monomial(2)
                sage: g = 2*G.monomial(3) +     G.monomial(4)
                sage: h =   H.monomial(5) +     H.monomial(6)

                sage: GH  = tensor([G, H])
                sage: gh = GH._tensor_of_elements([g, h]); gh
                2*B[3] # B[5] + 2*B[3] # B[6] + B[4] # B[5] + B[4] # B[6]

                sage: FGH = tensor([F, G, H])
                sage: FGH._tensor_of_elements([f, g, h])
                2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6]

                sage: FGH._tensor_of_elements([f, gh])
                2*B[1] # B[3] # B[5] + 2*B[1] # B[3] # B[6] + B[1] # B[4] # B[5] + B[1] # B[4] # B[6] + 4*B[2] # B[3] # B[5] + 4*B[2] # B[3] # B[6] + 2*B[2] # B[4] # B[5] + 2*B[2] # B[4] # B[6]
            """
            return self.tensor_constructor(tuple(element.parent() for element in elements))(*elements)

        def _coerce_map_from_(self, R):
            """
            Return ``True`` if there is a coercion from ``R`` into ``self`` and
            ``False`` otherwise.  The things that coerce into ``self`` are:

            - Anything with a coercion into ``self.base_ring()``.

            - A tensor algebra whose factors have a coercion into the
              corresponding factors of ``self``.

            TESTS::

                sage: C = CombinatorialFreeModule(ZZ, ZZ)
                sage: C2 = CombinatorialFreeModule(ZZ, NN)
                sage: M = C.module_morphism(lambda x: C2.monomial(abs(x)), codomain=C2)
                sage: M.register_as_coercion()
                sage: C2(C.basis()[3])
                B[3]
                sage: C2(C.basis()[3] + C.basis()[-3])
                2*B[3]
                sage: S = C.tensor(C)
                sage: S2 = C2.tensor(C2)
                sage: S2.has_coerce_map_from(S)
                True
                sage: S.has_coerce_map_from(S2)
                False
                sage: S.an_element()
                3*B[0] # B[-1] + 2*B[0] # B[0] + 2*B[0] # B[1]
                sage: S2(S.an_element())
                2*B[0] # B[0] + 5*B[0] # B[1]

            ::

                sage: C = CombinatorialFreeModule(ZZ, Set([1,2]))
                sage: D = CombinatorialFreeModule(ZZ, Set([2,4]))
                sage: f = C.module_morphism(on_basis=lambda x: D.monomial(2*x), codomain=D)
                sage: f.register_as_coercion()
                sage: T = tensor((C,C))
                sage: p = D.an_element()
                sage: T(tensor((p,p)))
                Traceback (most recent call last):
                ...
                NotImplementedError
                sage: T = tensor((D,D))
                sage: p = C.an_element()
                sage: T(tensor((p,p)))
                4*B[2] # B[2] + 4*B[2] # B[4] + 4*B[4] # B[2] + 4*B[4] # B[4]
            """
            if ((R in ModulesWithBasis(self.base_ring()).TensorProducts() or
                 R in GradedAlgebrasWithBasis(self.base_ring()).SignedTensorProducts())
                and isinstance(R, CombinatorialFreeModule_Tensor)
                and len(R._sets) == len(self._sets)
                and all(self._sets[i].has_coerce_map_from(M)
                        for i, M in enumerate(R._sets))):
                modules = R._sets
                vector_map = [self._sets[i]._internal_coerce_map_from(M)
                              for i, M in enumerate(modules)]
                return R.module_morphism(lambda x: self._tensor_of_elements(
                    [vector_map[i](M.monomial(x[i]))
                     for i, M in enumerate(modules)]), codomain=self)

            return super(CombinatorialFreeModule_Tensor, self)._coerce_map_from_(R)


class CartesianProductWithFlattening(object):
    """
    A class for Cartesian product constructor, with partial flattening
    """
    def __init__(self, flatten):
        """
        INPUT:

         - ``flatten`` -- a tuple of booleans

        This constructs a callable which accepts ``len(flatten)``
        arguments, and builds a tuple out them. When ``flatten[i]``,
        the i-th argument itself should be a tuple which is flattened
        in the result.

            sage: from sage.combinat.free_module import CartesianProductWithFlattening
            sage: CartesianProductWithFlattening([True, False, True, True])
            <sage.combinat.free_module.CartesianProductWithFlattening object at ...>

        """
        self._flatten = flatten

    def __call__(self, *indices):
        """
        EXAMPLES::

            sage: from sage.combinat.free_module import CartesianProductWithFlattening
            sage: cp = CartesianProductWithFlattening([True, False, True, True])
            sage: cp((1,2), (3,4), (5,6), (7,8))
            (1, 2, (3, 4), 5, 6, 7, 8)
            sage: cp((1,2,3), 4, (5,6), (7,8))
            (1, 2, 3, 4, 5, 6, 7, 8)

        """
        return sum((i if flatten else (i,)
                    for (i, flatten) in zip(indices, self._flatten)), ())


# TODO: find a way to avoid this hack to allow for cross references
CombinatorialFreeModule.Tensor = CombinatorialFreeModule_Tensor


class CombinatorialFreeModule_CartesianProduct(CombinatorialFreeModule):
    """
    An implementation of Cartesian products of modules with basis

    EXAMPLES:

    We construct two free modules, assign them short names, and construct their Cartesian product::

        sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F"
        sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G"
        sage: H = CombinatorialFreeModule(ZZ, [4,7]); H.__custom_name = "H"
        sage: S = cartesian_product([F, G])
        sage: S
        F (+) G
        sage: S.basis()
        Lazy family (Term map from Disjoint union of Family ({4, 5}, {4, 6}) to F (+) G(i))_{i in Disjoint union of Family ({4, 5}, {4, 6})}

    Note that the indices of the basis elements of F and G intersect non
    trivially. This is handled by forcing the union to be disjoint::

        sage: list(S.basis())
        [B[(0, 4)], B[(0, 5)], B[(1, 4)], B[(1, 6)]]

    We now compute the Cartesian product of elements of free modules::

        sage: f =   F.monomial(4) + 2 * F.monomial(5)
        sage: g = 2*G.monomial(4) +     G.monomial(6)
        sage: h =   H.monomial(4) +     H.monomial(7)
        sage: cartesian_product([f,g])
        B[(0, 4)] + 2*B[(0, 5)] + 2*B[(1, 4)] + B[(1, 6)]
        sage: cartesian_product([f,g,h])
        B[(0, 4)] + 2*B[(0, 5)] + 2*B[(1, 4)] + B[(1, 6)] + B[(2, 4)] + B[(2, 7)]
        sage: cartesian_product([f,g,h]).parent()
        F (+) G (+) H

    TODO: choose an appropriate semantic for Cartesian products of Cartesian products (associativity?)::

        sage: S = cartesian_product([cartesian_product([F, G]), H]) # todo: not implemented
        F (+) G (+) H
    """

    def __init__(self, modules, **options):
        r"""
        TESTS::

            sage: F = CombinatorialFreeModule(ZZ, [2,4,5])
            sage: G = CombinatorialFreeModule(ZZ, [2,4,7])
            sage: C = cartesian_product([F, G]) ; C
            Free module generated by {2, 4, 5} over Integer Ring (+) Free module generated by {2, 4, 7} over Integer Ring
            sage: TestSuite(C).run()
        """
        assert(len(modules))  # TODO: generalize to a family or tuple
        R = modules[0].base_ring()
        assert(all(module in ModulesWithBasis(R)) for module in modules)
        # should check the base ring
        self._sets = modules
        CombinatorialFreeModule.__init__(self, R,
            DisjointUnionEnumeratedSets(
                [module.basis().keys() for module in modules], keepkey=True),
            **options)

    def _sets_keys(self):
        """
        In waiting for self._sets.keys()

        TESTS::

            sage: F = CombinatorialFreeModule(ZZ, [2,4,5])
            sage: G = CombinatorialFreeModule(ZZ, [2,4,7])
            sage: CP = cartesian_product([F, G])
            sage: CP._sets_keys()
            [0, 1]
        """
        return list(range(len(self._sets)))

    def _repr_(self):
        """
        TESTS::

            sage: F = CombinatorialFreeModule(ZZ, [2,4,5])
            sage: CP = cartesian_product([F, F]); CP  # indirect doctest
            Free module generated by {2, 4, 5} over Integer Ring (+) Free module generated by {2, 4, 5} over Integer Ring
            sage: F.__custom_name = "F"; CP
            F (+) F
        """
        from sage.categories.cartesian_product import cartesian_product
        return cartesian_product.symbol.join("%s" % module
                                             for module in self._sets)
        # TODO: make this overridable by setting _name

    @cached_method
    def cartesian_embedding(self, i):
        """
        Return the natural embedding morphism of the ``i``-th
        Cartesian factor (summand) of ``self`` into ``self``.

        INPUT:

         - ``i`` -- an integer

        EXAMPLES::

            sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F"
            sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G"
            sage: S = cartesian_product([F, G])
            sage: phi = S.cartesian_embedding(0)
            sage: phi(F.monomial(4) + 2 * F.monomial(5))
            B[(0, 4)] + 2*B[(0, 5)]
            sage: phi(F.monomial(4) + 2 * F.monomial(6)).parent() == S
            True

        TESTS::

            sage: phi(G.monomial(4))
            Traceback (most recent call last):
            ...
            AssertionError
        """
        assert i in self._sets_keys()
        return self._sets[i]._module_morphism(lambda t: self.monomial((i, t)),
                                              codomain=self)

    summand_embedding = cartesian_embedding

    @cached_method
    def cartesian_projection(self, i):
        """
        Return the natural projection onto the `i`-th Cartesian factor
        (summand) of ``self``.

        INPUT:

         - ``i`` -- an integer

        EXAMPLES::

            sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F"
            sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G"
            sage: S = cartesian_product([F, G])
            sage: x = S.monomial((0,4)) + 2 * S.monomial((0,5)) + 3 * S.monomial((1,6))
            sage: S.cartesian_projection(0)(x)
            B[4] + 2*B[5]
            sage: S.cartesian_projection(1)(x)
            3*B[6]
            sage: S.cartesian_projection(0)(x).parent() == F
            True
            sage: S.cartesian_projection(1)(x).parent() == G
            True
        """
        assert i in self._sets_keys()
        module = self._sets[i]
        return self._module_morphism(lambda j_t: module.monomial(j_t[1]) if i == j_t[0] else module.zero(), codomain=module)

    summand_projection = cartesian_projection

    def _cartesian_product_of_elements(self, elements):
        """
        Return the Cartesian product of the elements.

        INPUT:

        - ``elements`` -- an iterable (e.g. tuple, list) with one element of
          each Cartesian factor of ``self``

        EXAMPLES::

            sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F"
            sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G"
            sage: S = cartesian_product([F, G])
            sage: f =   F.monomial(4) + 2 * F.monomial(5)
            sage: g = 2*G.monomial(4) +     G.monomial(6)
            sage: S._cartesian_product_of_elements([f, g])
            B[(0, 4)] + 2*B[(0, 5)] + 2*B[(1, 4)] + B[(1, 6)]
            sage: S._cartesian_product_of_elements([f, g]).parent() == S
            True

        TESTS:

        The ``elements`` can be a generator as in :trac:`31453`::

            sage: from sage.categories.magmatic_algebras import (
            ....:     MagmaticAlgebras
            ....: )
            sage: class TrivialCFM(CombinatorialFreeModule):
            ....:     def __init__(self):
            ....:         c = MagmaticAlgebras(QQ).WithBasis().Unital()
            ....:         super().__init__(QQ,[1],category=c)
            ....:
            ....:     def one(self):
            ....:         return self.monomial(0)
            ....:
            sage: c1 = TrivialCFM()
            sage: c1.one()
            B[0]
            sage: CP = cartesian_product([c1,c1])
            sage: CP.one()
            B[(0, 0)] + B[(1, 0)]

        """
        return self.sum(self.summand_embedding(i)(element_i)
                        for (i, element_i) in zip(self._sets_keys(),
                                                  elements))

    def cartesian_factors(self):
        """
        Return the factors of the Cartesian product.

        EXAMPLES::

            sage: F = CombinatorialFreeModule(ZZ, [4,5]); F.__custom_name = "F"
            sage: G = CombinatorialFreeModule(ZZ, [4,6]); G.__custom_name = "G"
            sage: S = cartesian_product([F, G])
            sage: S.cartesian_factors()
            (F, G)

        """
        return self._sets

    class Element(CombinatorialFreeModule.Element):  # TODO: get rid of this inheritance
        pass


CombinatorialFreeModule.CartesianProduct = CombinatorialFreeModule_CartesianProduct
